﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;

using System.Windows.Forms;
using System.Collections.Specialized;
using System.Xml;
using ZedGraph;

namespace Dazzle {
    public class GraphRenderer {
        private GraphBox _graphBox;
        private ILog logger = LogManager.GetLogger(typeof(GraphRenderer));

        //private static readonly Type MWARRAY_TYPE = typeof(MWArray);

        public GraphBox GraphBox {
            get { return _graphBox; }
            private set { _graphBox = value; }
        }

        private Font _font;
        public static short CF_EMBEDDEDOBJECT = Win32.RegisterClipboardFormat(Win32.CFSTR_EMBEDDEDOBJECT);
        public static short CF_EMBEDSOURCE = Win32.RegisterClipboardFormat(Win32.CFSTR_EMBEDSOURCE);
        public static short CF_LINKSOURCE = Win32.RegisterClipboardFormat(Win32.CFSTR_LINKSOURCE);
        public static short CF_OBJECTDESCRIPTOR = Win32.RegisterClipboardFormat(Win32.CFSTR_OBJECTDESCRIPTOR);
        public static short CF_LINKSRCDESCRIPTOR = Win32.RegisterClipboardFormat(Win32.CFSTR_LINKSRCDESCRIPTOR);

        public GraphRenderer(GraphBox graphBox)
        {
            GraphBox = graphBox;
            _font = new Font("Arial", 12);
        }

        IList<Rendering> Renderings {
            get {
                List<Rendering> renderings = new List<Rendering>();

                // We add them in order of preference.  Embedding is best fidelty
                // (because user can resize and edit), then metafile because it
                // will print well, and then bitmaps.

                // Allows us to be embedded with an OLE container.
                // No callback because this should go via GetDataHere.
                AddFormat(CF_EMBEDSOURCE, TYMED.TYMED_ISTORAGE, null, renderings);

                //AddFormat(CF_OBJECTDESCRIPTOR, TYMED.TYMED_HGLOBAL, RenderObjectDescriptor, renderings);

                // Nice because it is resolution independent.
                AddFormat(Win32.CF_ENHMETAFILE, TYMED.TYMED_ENHMF, RenderEnhMetaFile, renderings);

                // Nice because it is resolution independent.
                AddFormat(Win32.CF_METAFILEPICT, TYMED.TYMED_MFPICT, RenderMetaFile, renderings);

                // Nice because it lets us paste into e-mail, etc.
                //AddFormat(Win32.CF_DIB, TYMED.TYMED_HGLOBAL, RenderDIB, renderings);

                // Because it is an easy format to support.
                AddFormat(Win32.CF_BITMAP, TYMED.TYMED_GDI, RenderHBitmap, renderings);

                //data.AddFormat(CF_TEXT, TYMED.TYMED_HGLOBAL, RenderTextData);

                // And allow linking, where we have a moniker.  This is last because
                // it should not happen by default.
                if (_graphBox.Moniker != null) {
                    AddFormat(CF_LINKSOURCE, TYMED.TYMED_ISTREAM, RenderLink, renderings);
                    AddFormat(CF_LINKSRCDESCRIPTOR, TYMED.TYMED_HGLOBAL, RenderObjectDescriptor, renderings);
                }

                return renderings;
            }
        }

        private void AddFormat(short format, TYMED tymed, RenderData renderer, List<Rendering> renderings) {
            Rendering rendering = new Rendering();
            rendering.format = new FORMATETC();
            rendering.format.cfFormat = format;
            rendering.format.ptd = IntPtr.Zero;
            rendering.format.dwAspect = DVASPECT.DVASPECT_CONTENT;
            rendering.format.lindex = -1 /*all*/;
            rendering.format.tymed = tymed;
            rendering.renderer = renderer;
            renderings.Add(rendering);
        }

        #region Rendering

        IntPtr RenderObjectDescriptor(TYMED tymed) {
            Debug.Assert(tymed == TYMED.TYMED_HGLOBAL);
            // Fill in the basic information.
            Win32.OBJECTDESCRIPTOR od = new Win32.OBJECTDESCRIPTOR();
            // According to the documentation this is used just to find an icon.
            od.clsid = GraphBox.CLSID;
            od.dwDrawAspect = DVASPECT.DVASPECT_CONTENT;
            od.sizelcx = 0; // zero in imitation of Word/Excel, but could be box.Extent.cx;
            od.sizelcy = 0; // zero in imitation of Word/Excel, but could be box.Extent.cy;
            od.pointlx = 0;
            od.pointly = 0;
            od.dwStatus = _graphBox.MiscStatus((int)od.dwDrawAspect);

            // Descriptive strings to tack on after the struct.
            string name = GraphBox.USER_TYPE;
            int name_size = (name.Length + 1) * sizeof(char);
            string source = "Box Meister";
            int source_size = (source.Length + 1) * sizeof(char);
            int od_size = Marshal.SizeOf(typeof(Win32.OBJECTDESCRIPTOR));
            od.dwFullUserTypeName = od_size;
            od.dwSrcOfCopy = od_size + name_size;
            int full_size = od_size + name_size + source_size;
            od.cbSize = full_size;

            // To avoid 'unsafe', we will arrange the strings in a byte array.
            byte[] strings = new byte[full_size];
            Encoding unicode = Encoding.Unicode;
            Array.Copy(unicode.GetBytes(name), 0, strings, od.dwFullUserTypeName, name.Length * sizeof(char));
            Array.Copy(unicode.GetBytes(source), 0, strings, od.dwSrcOfCopy, source.Length * sizeof(char));

            // Combine the strings and the struct into a single block of mem.
            IntPtr hod = Win32.GlobalAlloc(Win32.GHND, full_size);
            Debug.Assert(hod != IntPtr.Zero);
            IntPtr buf = Win32.GlobalLock(hod);
            Marshal.Copy(strings, 0, buf, full_size);
            Marshal.StructureToPtr(od, buf, false);

            Win32.GlobalUnlock(hod);
            return hod;
        }

        /// <summary>Create a memory metafile and return its handle.
        /// 
        /// This will fail to convert bezier curves because they are not
        /// supported in ye olde Windows metafile.  Unfortunately we must
        /// support this format because it is all that Word and Excel are
        /// willing to use!</summary>
        IntPtr RenderMetaFile(TYMED tymed) {
            //Debug.Assert(tymed == TYMED.TYMED_MFPICT);
            IntPtr hmf = IntPtr.Zero;
            logger.Debug("RenderMetaFile(TYMED tymed)");
 

            IntPtr hDC = Win32.CreateMetaFile(null);
            Debug.Assert(hDC != IntPtr.Zero);

            // This is absolutely essential to the metafile so it
            // can be scaled in the clipboard and any destination
            // application.
            Win32.SetMapMode(hDC, Win32.MM_ANISOTROPIC);
            Win32.SetWindowOrgEx(hDC, 0, 0, IntPtr.Zero);
            int dpi = 96;
            Win32.SetWindowExtEx(hDC, HIMETRICToPixels(GraphBox.Extent.cx, dpi), HIMETRICToPixels(GraphBox.Extent.cy, dpi), IntPtr.Zero);
            RenderWin32(hDC, dpi, dpi, "Windows Metafile");

            hmf = Win32.CloseMetaFile(hDC);

            // Convert it to a METAFILEPICT.
            IntPtr hMem = Win32.GlobalAlloc(Win32.GHND, Marshal.SizeOf(typeof(Win32.METAFILEPICT)));
            Win32.METAFILEPICT mfp = new Win32.METAFILEPICT();
            mfp.mm = Win32.MM_ANISOTROPIC;
            mfp.xExt = GraphBox.Extent.cx;
            mfp.yExt = GraphBox.Extent.cy;

            mfp.hMF = hmf;
            Marshal.StructureToPtr(mfp, Win32.GlobalLock(hMem), false);
            Win32.GlobalUnlock(hMem);

            return hMem;
        }

        // This code basically copied from http://support.microsoft.com/kb/145999
        IntPtr RenderEnhMetaFile(TYMED tymed) {
            IntPtr meta_dc;

            IntPtr screen_dc = Win32.GetDC(IntPtr.Zero);

            double inches_x = GraphBox.Extent.cx / 2540.0;
            double inches_y = GraphBox.Extent.cy / 2540.0;

            /*
             * Number of pixels per logical inch along the screen width. 
             * In a system with multiple display monitors, this value is the same for all monitors.
             * 
             * Number of pixels per logical inch along the screen height. 
             * In a system with multiple display monitors, this value is the same for all monitors.
             * 
             */
            int dpi_x = Win32.GetDeviceCaps(screen_dc, Win32.LOGPIXELSX);
            int dpi_y = Win32.GetDeviceCaps(screen_dc, Win32.LOGPIXELSY);

            //int t = Win32.GetDeviceCaps(screen_dc, (int)DeviceCap.TEXTCAPS);

            /*
             * Width, in millimeters, of the physical screen. 
             * 
             * Height, in millimeters, of the physical screen.
             * 
             */
            int iWidthMM = Win32.GetDeviceCaps(screen_dc, Win32.HORZSIZE);
            int iHeightMM = Win32.GetDeviceCaps(screen_dc, Win32.VERTSIZE);

            /*
             * Width, in pixels, of the screen; or for printers, 
             * the width, in pixels, of the printable area of the page.
             * 
             * Height, in raster lines, of the screen; or for printers, 
             * the height, in pixels, of the printable area of the page. 
             * 
             */
            int iWidthPels = Win32.GetDeviceCaps(screen_dc, Win32.HORZRES);
            int iHeightPels = Win32.GetDeviceCaps(screen_dc, Win32.VERTRES);

            // Extent in .01mm units (HIMETRIC).
            Win32.RECT extent = new Win32.RECT();
            extent.Left = 0;
            extent.Top = 0;

            // in .01-millimeter units
            extent.Right = GraphBox.Extent.cx;
            extent.Bottom = GraphBox.Extent.cy;

            logger.Debug(String.Format("Rendering {0}x{1}", extent.Right, extent.Bottom));

            try {
                logger.Debug(string.Format("显示器物理尺寸:{0}mm X {1}mm;分辨率:{2} X {3},每逻辑英寸的像素数:{4} X {5}", iWidthMM, iHeightMM,
                    iWidthPels, iHeightPels, dpi_x, dpi_y));

                meta_dc = Win32.CreateEnhMetaFile(screen_dc, null, ref extent, "fiddling\0blue box\0\0");
            } finally {
                Win32.ReleaseDC(IntPtr.Zero, screen_dc);
            }

            // Anisotropic mapping mode
            Win32.SetMapMode(meta_dc, Win32.MM_ANISOTROPIC);

            // Win32.SetWindowOrgEx(meta_dc, 100, 100, IntPtr.Zero);
            // Win32.SetViewportExtEx
            // Set the Windows extent
            Win32.SetWindowExtEx(meta_dc, (int)(inches_x * dpi_x), (int)(inches_y * dpi_y), IntPtr.Zero);
            
            // Set the viewport extent to reflect
            // dwInchesX" x dwInchesY" in device units
            Win32.SetViewportExtEx(meta_dc,
                (int)((float)inches_x * 25.4f * iWidthPels / iWidthMM),
                (int)((float)inches_y * 25.4f * iHeightPels / iHeightMM),
                IntPtr.Zero);

            /*
             * Convert mm to px.
             */
            int imgWidth = (int)(inches_x * dpi_x);
            int imgHeight = (int)(inches_y * dpi_y);

            logger.Debug("图片尺寸：{0} X {1}", imgWidth, imgHeight);

            if (GraphBox.IsDirty() == 0) {
                using (Graphics g = Graphics.FromHdc(meta_dc)) {
                    GetPane();

                    GraphBox.Chart.Width = imgWidth;
                    GraphBox.Chart.Height = imgHeight;

                    logger.Debug("Image: {0} {1}", imgWidth, imgHeight);
                    g.DrawImage(GraphBox.Chart.MasterPane.GetImage(imgWidth, imgHeight, 96), 0f, 0f);
                }
            }

            IntPtr hemf = Win32.CloseEnhMetaFile(meta_dc);

            Win32.ENHMETAHEADER emfh = Win32.EasyGetEnhMetaFileHeader(hemf);

            return hemf;
        }

        

        void GetPane() {
            // Get a reference to the GraphPane instance in the ZedGraphControl
            GraphPane myPane = GraphBox.Chart.GraphPane;

            // Set the titles and axis labels
            myPane.Title.Text = "Demonstration of Dual Y Graph";
            myPane.XAxis.Title.Text = "Time, Days";
            myPane.YAxis.Title.Text = "Parameter A";
            myPane.Y2Axis.Title.Text = "Parameter B";

            // Make up some data points based on the Sine function
            PointPairList list = new PointPairList();
            PointPairList list2 = new PointPairList();
            for (int i = 0; i < 36; i++) {
                double x = (double)i * 5.0;
                double y = Math.Sin((double)i * Math.PI / 15.0) * 16.0;
                double y2 = y * 13.5;
                list.Add(x, y);
                list2.Add(x, y2);
            }

            myPane.CurveList.Clear();

            // Generate a red curve with diamond symbols, and "Alpha" in the legend
            LineItem myCurve = myPane.AddCurve("Alpha",
                list, Color.Red, SymbolType.Diamond);
            // Fill the symbols with white
            myCurve.Symbol.Fill = new Fill(Color.White);

            // Generate a blue curve with circle symbols, and "Beta" in the legend
            myCurve = myPane.AddCurve("Beta",
                list2, Color.Blue, SymbolType.Circle);
            // Fill the symbols with white
            myCurve.Symbol.Fill = new Fill(Color.White);
            // Associate this curve with the Y2 axis
            myCurve.IsY2Axis = true;

            // Show the x axis grid
            myPane.XAxis.MajorGrid.IsVisible = true;

            // Make the Y axis scale red
            myPane.YAxis.Scale.FontSpec.FontColor = Color.Red;
            myPane.YAxis.Title.FontSpec.FontColor = Color.Red;
            // turn off the opposite tics so the Y tics don't show up on the Y2 axis
            myPane.YAxis.MajorTic.IsOpposite = false;
            myPane.YAxis.MinorTic.IsOpposite = false;
            // Don't display the Y zero line
            myPane.YAxis.MajorGrid.IsZeroLine = false;
            // Align the Y axis labels so they are flush to the axis
            myPane.YAxis.Scale.Align = AlignP.Inside;
            // Manually set the axis range
            myPane.YAxis.Scale.Min = -30;
            myPane.YAxis.Scale.Max = 30;

            // Enable the Y2 axis display
            myPane.Y2Axis.IsVisible = true;
            // Make the Y2 axis scale blue
            myPane.Y2Axis.Scale.FontSpec.FontColor = Color.Blue;
            myPane.Y2Axis.Title.FontSpec.FontColor = Color.Blue;
            // turn off the opposite tics so the Y2 tics don't show up on the Y axis
            myPane.Y2Axis.MajorTic.IsOpposite = false;
            myPane.Y2Axis.MinorTic.IsOpposite = false;
            // Display the Y2 axis grid lines
            myPane.Y2Axis.MajorGrid.IsVisible = true;
            // Align the Y2 axis labels so they are flush to the axis
            myPane.Y2Axis.Scale.Align = AlignP.Inside;

            // Fill the axis background with a gradient
            myPane.Chart.Fill = new Fill(Color.White, Color.LightGray, 45.0f);

            // Add a text box with instructions
            TextObj text = new TextObj(
                "Zoom: left mouse & drag\nPan: middle mouse & drag\nContext Menu: right mouse",
                0.05f, 0.95f, CoordType.ChartFraction, AlignH.Left, AlignV.Bottom);
            text.FontSpec.StringAlignment = StringAlignment.Near;
            myPane.GraphObjList.Add(text);

            // Enable scrollbars if needed
            GraphBox.Chart.IsShowHScrollBar = true;
            GraphBox.Chart.IsShowVScrollBar = true;
            GraphBox.Chart.IsAutoScrollRange = true;
            GraphBox.Chart.IsScrollY2 = true;

            // OPTIONAL: Show tooltips when the mouse hovers over a point
            GraphBox.Chart.IsShowPointValues = true;
            GraphBox.Chart.PointValueEvent += new ZedGraphControl.PointValueHandler(MyPointValueHandler);

            // OPTIONAL: Add a custom context menu item
            GraphBox.Chart.ContextMenuBuilder += new ZedGraphControl.ContextMenuBuilderEventHandler(
                            MyContextMenuBuilder);

            // OPTIONAL: Handle the Zoom Event
            GraphBox.Chart.ZoomEvent += new ZedGraphControl.ZoomEventHandler(MyZoomEvent);


            // Tell ZedGraph to calculate the axis ranges
            // Note that you MUST call this after enabling IsAutoScrollRange, since AxisChange() sets
            // up the proper scrolling parameters
            GraphBox.Chart.AxisChange();
            // Make sure the Graph gets redrawn
            GraphBox.Chart.Invalidate();
        }

 
        /// <summary>
        /// Display customized tooltips when the mouse hovers over a point
        /// </summary>
        private string MyPointValueHandler(ZedGraphControl control, GraphPane pane,
                        CurveItem curve, int iPt) {
            // Get the PointPair that is under the mouse
            PointPair pt = curve[iPt];

            return curve.Label.Text + " is " + pt.Y.ToString("f2") + " units at " + pt.X.ToString("f1") + " days";
        }

        /// <summary>
        /// Customize the context menu by adding a new item to the end of the menu
        /// </summary>
        private void MyContextMenuBuilder(ZedGraphControl control, ContextMenuStrip menuStrip,
                        Point mousePt, ZedGraphControl.ContextMenuObjectState objState) {
            ToolStripMenuItem item = new ToolStripMenuItem();
            item.Name = "add-beta";
            item.Tag = "add-beta";
            item.Text = "Add a new Beta Point";
            item.Click += new System.EventHandler(AddBetaPoint);

            menuStrip.Items.Add(item);
        }

        /// <summary>
        /// Handle the "Add New Beta Point" context menu item.  This finds the curve with
        /// the CurveItem.Label = "Beta", and adds a new point to it.
        /// </summary>
        private void AddBetaPoint(object sender, EventArgs args) {
            // Get a reference to the "Beta" curve IPointListEdit
            IPointListEdit ip = GraphBox.Chart.GraphPane.CurveList["Beta"].Points as IPointListEdit;
            if (ip != null) {
                double x = ip.Count * 5.0;
                double y = Math.Sin(ip.Count * Math.PI / 15.0) * 16.0 * 13.5;
                ip.Add(x, y);
                GraphBox.Chart.AxisChange();
                GraphBox.Chart.Refresh();
            }
        }

        // Respond to a Zoom Event
        private void MyZoomEvent(ZedGraphControl control, ZoomState oldState,
                    ZoomState newState) {
            // Here we get notification everytime the user zooms
        }

        IntPtr RenderHBitmap(TYMED tymed) {
            //Debug.Assert(tymed == TYMED.TYMED_GDI);

            // Create an HBITMAP the hard way.
            IntPtr hDC = Win32.GetDC(IntPtr.Zero);
            IntPtr hMemDC = Win32.CreateCompatibleDC(hDC);
            int dpi_x = Win32.GetDeviceCaps(hDC, Win32.LOGPIXELSX);
            int dpi_y = Win32.GetDeviceCaps(hDC, Win32.LOGPIXELSY);
            IntPtr hBmp = Win32.CreateCompatibleBitmap(hDC, HIMETRICToPixels(GraphBox.Extent.cx, dpi_x), HIMETRICToPixels(GraphBox.Extent.cy, dpi_y));
            if (IntPtr.Zero != hBmp) {
                IntPtr hObj = Win32.SelectObject(hMemDC, hBmp);
                using (Graphics g = Graphics.FromHdc(hMemDC)) {
                    RenderGraphics(g, "Bitmap");
                }
                Win32.SelectObject(hMemDC, hObj);
            }
            Win32.DeleteDC(hMemDC);
            Win32.ReleaseDC(IntPtr.Zero, hDC);
            return hBmp;
        }

        void RenderWin32(IntPtr hdc, int dpi_x, int dpi_y, string mode) {
            Rectangle background, square;
            GetCoords(dpi_x, dpi_y, out background, out square);
            IntPtr white_brush = IntPtr.Zero, blue_brush = IntPtr.Zero, red_pen = IntPtr.Zero,
                   hfont = IntPtr.Zero;
            try {
                // Warning: If you change this code then change RenderGraphics
                // too.
                Win32.RECT RECTbackground = new Win32.RECT();
                RECTbackground.Left = background.Left;
                RECTbackground.Top = background.Top;
                RECTbackground.Right = background.Right;
                RECTbackground.Bottom = background.Bottom;
                white_brush = Win32.CreateSolidBrush(0x00FFFFFF);
                Win32.FillRect(hdc, ref RECTbackground, white_brush);
                Win32.RECT RECTsquare = new Win32.RECT();
                RECTsquare.Left = square.Left;
                RECTsquare.Top = square.Top;
                RECTsquare.Right = square.Right;
                RECTsquare.Bottom = square.Bottom;
                blue_brush = Win32.CreateSolidBrush(0x00FF8080);
                Win32.FillRect(hdc, ref RECTsquare, blue_brush);
                red_pen = Win32.CreatePen(0/*solid*/, 3, 0x000000FF);
                Win32.SelectObject(hdc, red_pen);
                Win32.MoveToEx(hdc, RECTsquare.Left, RECTsquare.Top, IntPtr.Zero);
                Win32.LineTo(hdc, RECTsquare.Right, RECTsquare.Bottom);
                Win32.MoveToEx(hdc, RECTsquare.Left, RECTsquare.Bottom, IntPtr.Zero);
                Win32.LineTo(hdc, RECTsquare.Right, RECTsquare.Top);
                hfont = _font.ToHfont();
                Win32.SelectObject(hdc, hfont);
                Win32.TextOut(hdc, 0, 0, GraphBox.Chart.Name, GraphBox.Chart.Name.Length);
                Win32.TextOut(hdc, 0, _font.Height, mode, mode.Length);
                Win32.TextOut(hdc, 0, _font.Height * 2, GraphBox.Chart.Name, GraphBox.Chart.Name.Length);
            } finally {
                if (white_brush != IntPtr.Zero)
                    Win32.DeleteObject(white_brush);
                if (blue_brush != IntPtr.Zero)
                    Win32.DeleteObject(blue_brush);
                if (red_pen != IntPtr.Zero)
                    Win32.DeleteObject(red_pen);
                if (hfont != IntPtr.Zero)
                    Win32.DeleteObject(hfont);
            }
        }

        IntPtr RenderDIB(TYMED tymed) {
            //Debug.Assert(tymed == TYMED.TYMED_HGLOBAL);
            Bitmap bitmap = RenderBitmap("DIB");
            MemoryStream mem = new MemoryStream();
            // ImageFormat.MemoryBmp work (will save to PNG!).  Therefore use
            // BMP and strip header.
            bitmap.Save(mem, ImageFormat.Bmp);
            byte[] bmp = mem.ToArray();

            int offset = Marshal.SizeOf(typeof(Win32.BITMAPFILEHEADER));
            IntPtr hdib = Win32.GlobalAlloc(Win32.GHND, (int)(mem.Length - offset));
            Debug.Assert(hdib != IntPtr.Zero);
            IntPtr buf = Win32.GlobalLock(hdib);
            Marshal.Copy(bmp, offset, buf, (int)mem.Length - offset);
            Win32.GlobalUnlock(hdib);

            return hdib;
        }

        void GetCoords(float dpi_x, float dpi_y, out Rectangle background, out Rectangle square) {
            // Convert HIMETRIC to pixels.
            int width = HIMETRICToPixels(GraphBox.Extent.cx, dpi_x);
            int height = HIMETRICToPixels(GraphBox.Extent.cy, dpi_y);
            background = new Rectangle(0, 0, width, height);
            // Find the coordinates of a square centred within our render area.
            if (GraphBox.Extent.cx > GraphBox.Extent.cy)
                square = new Rectangle((width - height) / 2, 0, height, height);
            else
                square = new Rectangle(0, (height - width) / 2, width, width);
        }

        Bitmap RenderBitmap(string mode) {
            float dpi_x, dpi_y;
            using (Graphics g = new System.Windows.Forms.Form().CreateGraphics()) {
                dpi_x = g.DpiX;
                dpi_y = g.DpiY;
            }
            Bitmap bitmap = new Bitmap(HIMETRICToPixels(GraphBox.Extent.cx, dpi_x),
                                       HIMETRICToPixels(GraphBox.Extent.cy, dpi_y));
            using (Graphics g = Graphics.FromImage(bitmap)) {
                RenderGraphics(g, mode);
            }
            return bitmap;
        }

        int HIMETRICToPixels(int himetric, float dpi) {
            int x = (int)Math.Round((himetric / 2540.0) * dpi);
            logger.Debug(string.Format("Math.Round((himetric / 2540.0) * dpi) = {0}?himetric={1};dpi={2}", x, himetric, dpi));
            return x;
        }

        public void RenderGraphics(Graphics g, string mode) {
            RenderGraphics(g, mode, g.DpiX, g.DpiY);
        }

        public void RenderGraphics(Graphics g, string mode, float dpi_x, float dpi_y) {
            // Warning: If you change this code then change RenderWin32 too.
            Brush ltblue = new SolidBrush(Color.FromArgb(0x80, 0x80, 0xFF));
            Rectangle background, square;
            GetCoords(dpi_x, dpi_y, out background, out square);
            g.FillRectangle(Brushes.Blue, background);
            //g.FillRectangle(Brushes.LightBlue, square);
            Pen red_pen = new Pen(Color.Red, 3);
            g.DrawLine(red_pen, background.Left, background.Top, background.Right, background.Bottom);
            g.DrawLine(red_pen, background.Left, background.Bottom, background.Right, background.Top);
            g.DrawString(GraphBox.Chart.Name, _font, Brushes.Black, 0, 0);
            g.DrawString(mode, _font, Brushes.Black, 0, _font.Height);
            g.DrawString(GraphBox.Chart.Name, _font, Brushes.Black, 0, _font.Height * 2);


            logger.Debug("g.DrawImage(new Bitmap(\"F:\\45.png\"), new Point(0, 0));");
        }

        void SaveMonikerToStream(IMoniker moniker, IStream strm) {
            logger.Debug("SaveMonikerToStream:{0}", Win32.GetDisplayName(moniker));
            int hr = Win32.OleSaveToStream((IPersistStream)moniker, strm);
            Debug.Assert(hr == Win32.S_OK);
            // I write this in imitation of Brockschmidt, but I do not see why
            // it is here.
            // BoxesDoc seems best when linking because when Word reloads a
            // document containing saved images then this is the class it 
            // instantiates.  Otherwise it wrongly instantiates Box.
            // cf IOleObject.GetUserClassID
            //Win32.WriteClassStm(strm, BoxesDoc.ClsID);
            Debug.Assert(hr == Win32.S_OK);
        }

        //int attempt = 0;
        IntPtr RenderLink(TYMED tymed) {
            Debug.Assert(tymed == TYMED.TYMED_ISTREAM);
            IStream strm;
            int hr = Win32.CreateStreamOnHGlobal(IntPtr.Zero, true, out strm);
            Debug.Assert(hr == Win32.S_OK);
            SaveMonikerToStream(GraphBox.Moniker, strm);

            return Marshal.GetIUnknownForObject(strm);  // Increments ref count
        }

        #endregion

        #region Things from IDataObject

        public int QueryGetData(ref FORMATETC format) {
            // We only support CONTENT aspect
            if ((DVASPECT.DVASPECT_CONTENT & format.dwAspect) == 0)
                return Win32.DV_E_DVASPECT;

            int ret = Win32.DV_E_TYMED;

            logger.Debug("{0}", format.tymed);
            // Try to locate the data
            // TODO: The ret, if not S_OK, is only relevant to the last item
            foreach (Rendering rendering in Renderings) {
                if ((rendering.format.tymed & format.tymed) > 0) {
                    if (rendering.format.cfFormat == format.cfFormat) {
                        // Found it, return S_OK;
                        return Win32.S_OK;
                    } else {
                        // Found the medium type, but wrong format
                        ret = Win32.DV_E_FORMATETC;
                    }
                } else {
                    // Mismatch on medium type
                    ret = Win32.DV_E_TYMED;
                }
            }

            logger.Debug("   returning {0:x}", ret);
            return ret;
        }

        public void GetData(ref FORMATETC format, out STGMEDIUM medium) {
            try {
                // Locate the data
                foreach (Rendering rendering in Renderings) {
                    if ((rendering.format.tymed & format.tymed) > 0
                        && rendering.format.dwAspect == format.dwAspect
                        && rendering.format.cfFormat == format.cfFormat
                        && rendering.renderer != null) {
                        // Found it. Return a copy of the data.

                        medium = new STGMEDIUM();
                        medium.tymed = format.tymed;
                        medium.unionmember = rendering.renderer(format.tymed);
                        if (medium.tymed == TYMED.TYMED_ISTORAGE || medium.tymed == TYMED.TYMED_ISTREAM)
                            medium.pUnkForRelease = Marshal.GetObjectForIUnknown(medium.unionmember);
                        else
                            medium.pUnkForRelease = null;
                        return;
                    }
                }
            } catch (Exception e) {
                logger.Error("GetData occured an exception.", e);
                throw;
            }

            logger.Debug("-> DV_E_FORMATETC");
            medium = new STGMEDIUM();
            Marshal.ThrowExceptionForHR(Win32.DV_E_FORMATETC);
        }

        internal void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) {
            // Allows containers to duplicate this into their own storage.
            try {
                if (format.cfFormat == CF_EMBEDSOURCE && (format.tymed & TYMED.TYMED_ISTORAGE) != 0) {
                    medium.tymed = TYMED.TYMED_ISTORAGE;
                    medium.pUnkForRelease = null;
                    IStorage stg = (IStorage)Marshal.GetObjectForIUnknown(medium.unionmember);
                    
                    GraphBox.Save(stg, false);
                    return;
                }
                if (format.cfFormat == CF_LINKSOURCE && (format.tymed & TYMED.TYMED_ISTREAM) != 0
                           && GraphBox.Moniker != null) {
                    medium.tymed = TYMED.TYMED_ISTREAM;
                    medium.pUnkForRelease = null;
                    IStream strm = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
                    SaveMonikerToStream(GraphBox.Moniker, strm);
                    return;
                }
            } catch (Exception e) {
                logger.Error("GetDataHere occured an exception.", e);
                throw;
            }
            Marshal.ThrowExceptionForHR(Win32.DATA_E_FORMATETC);
        }

        public IEnumFORMATETC EnumFormatEtc() {
            return new EnumFORMATETC(Renderings);
        }

        #endregion

        #region EnumFORMATETC

        /// <summary>
        /// Helps enumerate the formats available in our DataObject class.
        /// </summary>
        [ComVisible(true)]
        private class EnumFORMATETC : IEnumFORMATETC {
            // Keep an array of the formats for enumeration
            private IList<Rendering> renderings;
            // The index of the next item
            private int currentIndex = 0;

            /// <summary>
            /// Creates an instance from a list of key value pairs.
            /// </summary>
            /// <param name="storage">List of FORMATETC/STGMEDIUM key value pairs</param>
            internal EnumFORMATETC(IList<Rendering> renderings) {
                this.renderings = renderings;
            }

            #region IEnumFORMATETC Members

            /// <summary>
            /// Creates a clone of this enumerator.
            /// </summary>
            /// <param name="newEnum">When this function returns, contains a new instance of IEnumFORMATETC.</param>
            public void Clone(out IEnumFORMATETC newEnum) {
                EnumFORMATETC ret = new EnumFORMATETC(renderings);
                ret.currentIndex = currentIndex;
                newEnum = ret;
            }

            /// <summary>
            /// Retrieves the next elements from the enumeration.
            /// </summary>
            /// <param name="celt">The number of elements to retrieve.</param>
            /// <param name="rgelt">An array to receive the formats requested.</param>
            /// <param name="pceltFetched">An array to receive the number of element fetched.</param>
            /// <returns>If the fetched number of formats is the same as the requested number, S_OK is returned.
            /// There are several reasons S_FALSE may be returned: (1) The requested number of elements is less than
            /// or equal to zero. (2) The rgelt parameter equals null. (3) There are no more elements to enumerate.
            /// (4) The requested number of elements is greater than one and pceltFetched equals null or does not
            /// have at least one element in it. (5) The number of fetched elements is less than the number of
            /// requested elements.</returns>
            public int Next(int celt, FORMATETC[] rgelt, int[] pceltFetched) {
                // Start with zero fetched, in case we return early
                if (pceltFetched != null && pceltFetched.Length > 0)
                    pceltFetched[0] = 0;

                // This will count down as we fetch elements
                int cReturn = celt;

                // Short circuit if they didn't request any elements, or didn't
                // provide room in the return array, or there are not more elements
                // to enumerate.
                if (celt <= 0 || rgelt == null || currentIndex >= renderings.Count)
                    return 1; // S_FALSE

                // If the number of requested elements is not one, then we must
                // be able to tell the caller how many elements were fetched.
                if ((pceltFetched == null || pceltFetched.Length < 1) && celt != 1)
                    return 1; // S_FALSE

                // If the number of elements in the return array is too small, we
                // throw. This is not a likely scenario, hence the exception.
                if (rgelt.Length < celt)
                    throw new ArgumentException("The number of elements in the return array is less than the number of elements requested");

                // Fetch the elements.
                for (int i = 0; currentIndex < renderings.Count && cReturn > 0; i++, cReturn--, currentIndex++)
                    rgelt[i] = renderings[currentIndex].format;

                // Return the number of elements fetched
                if (pceltFetched != null && pceltFetched.Length > 0)
                    pceltFetched[0] = celt - cReturn;

                // cReturn has the number of elements requested but not fetched.
                // It will be greater than zero, if multiple elements were requested
                // but we hit the end of the enumeration.
                return (cReturn == 0) ? 0 : 1; // S_OK : S_FALSE
            }

            /// <summary>
            /// Resets the state of enumeration.
            /// </summary>
            /// <returns>S_OK</returns>
            public int Reset() {
                currentIndex = 0;
                return 0; // S_OK
            }

            /// <summary>
            /// Skips the number of elements requested.
            /// </summary>
            /// <param name="celt">The number of elements to skip.</param>
            /// <returns>If there are not enough remaining elements to skip, returns S_FALSE. Otherwise, S_OK is returned.</returns>
            public int Skip(int celt) {
                if (currentIndex + celt > renderings.Count)
                    return 1; // S_FALSE

                currentIndex += celt;
                return 0; // S_OK
            }

            #endregion
        }

        #endregion

        public delegate IntPtr RenderData(TYMED tymed);

        struct Rendering {
            public FORMATETC format;
            public RenderData renderer;
        }
    }
}
